package myJs.myPkg

import cats.effect._
import japgolly.scalajs.react.Reusable
import japgolly.scalajs.react.hooks.Hooks.{UseEffectArg, UseState, UseStateF}
import japgolly.scalajs.react.internal.Box

import scala.annotation.unchecked.uncheckedVariance
import scala.scalajs.js
import scala.scalajs.js.JSConverters._
import scala.scalajs.js.annotation.{JSImport, JSName}
import scala.scalajs.js.|
import cats.effect._
import japgolly.scalajs.react.util.EffectCatsEffect._

/** Created by yz on 21/9/2023
  */

@js.native
@JSImport("react", JSImport.Namespace, "React")
object HooksRaw extends js.Object {

  final type UseStateSetter[S] = js.Function1[S | js.Function1[S, S], Unit]
  final type UseState[S] = js.Tuple2[S, UseStateSetter[S]]

  final def useState[S](initial: S | js.Function0[S]): UseState[S] = js.native

  final type UseEffectArg = js.Function0[js.UndefOr[js.Function0[Any]]]

  def useEffect(thunk: UseEffectArg, watchedObjects: js.Array[js.Any]): Unit = js.native

  def useEffect(thunk: js.Function0[EffectCallbackReturn]): Unit = js.native

  def useLayoutEffect(thunk: UseEffectArg, watchedObjects: js.Array[js.Any]): Unit = js.native

  def useRef[T](initialValue: T): ReactRef[T] = js.native

}

@js.native
trait EffectCallbackReturn extends js.Object

object EffectCallbackReturn {
  @inline implicit def fromFunction[T](fn: () => T): EffectCallbackReturn =
    (fn: js.Function0[T]).asInstanceOf[EffectCallbackReturn]

  @inline implicit def fromJSFunction[T](fn: js.Function0[T]): EffectCallbackReturn =
    fn.asInstanceOf[EffectCallbackReturn]

  @inline implicit def fromAny[T](value: T): EffectCallbackReturn =
    js.undefined.asInstanceOf[EffectCallbackReturn]
}

final class SetStateHookCallback[T](private val origFunction: js.Function1[js.Any, Unit]) extends AnyVal {
  @inline def apply(newState: T): Unit =
    origFunction.apply(newState.asInstanceOf[js.Any])

  @inline def apply(transformState: T => T): Unit =
    origFunction.apply(transformState: js.Function1[T, T])
}

@js.native
trait ReactRef[T] extends js.Object {
  var current: T @uncheckedVariance = js.native
}

object Hooks {

  case class MyUseState[S](useState: UseState[S]) {

    def apply(s: S) = {
      useState.setState(s)
    }

    def apply(f: S => S) = {
      useState.modState(f)
    }

    def value = useState.value

    def toSyncIO = (s: S) => {
      useState.setState(s)
    }

    def toHSyncIO = (s: S=>S) => {
      useState.modState(s)
    }

  }

  @inline def useState[T](initialState: => T): (T, MyUseState[T]) = {
    val initialStateFn = (() => Box(initialState)): js.Function0[Box[T]]
    val originalResult = HooksRaw.useState[Box[T]](initialStateFn)
    val originalSetState = Reusable.byRef(originalResult._2)
    val us = UseState(originalResult, originalSetState)
      .xmap(_.unbox)(Box.apply)
    (us.value, MyUseState(us))
  }

  @inline def useRef[T](initialValue: T): ReactRef[T] =
    HooksRaw.useRef[T](initialValue)

  @inline def useEffect[A](thunk: => A, watchedObjects: Iterable[Any])(implicit
      a: UseEffectArg[A]
  ): Unit =
    HooksRaw.useEffect(
      a.toJs(thunk),
      watchedObjects.toJSArray.asInstanceOf[js.Array[js.Any]]
    )

  @inline def useEffect[T](thunk: () => T)(implicit conv: T => EffectCallbackReturn): Unit =
    HooksRaw.useEffect(() => conv(thunk()))

  @inline def useLayoutEffect[A](thunk : => A, watchedObjects: Iterable[Any])(implicit
    a: UseEffectArg[A]
  ): Unit =
    HooksRaw.useLayoutEffect(
      () => a.toJs(thunk),
      watchedObjects.toJSArray.asInstanceOf[js.Array[js.Any]]
    )

}

@js.native
@JSImport("react", JSImport.Namespace, "React")
object ReactRaw extends js.Object{

  def createRef[T](): ReactRef[T] = js.native

}

object MyReact{

  def createRef[T]: ReactRef[T] = ReactRaw.createRef[T]()

}
